package org.goldenport.g3.messages

import scala.xml._
import javax.servlet.http.HttpServletResponse
import com.asamioffice.goldenport.text.StringUtil.makeShortString
import org.goldenport.g3.{G3Message, G3AgentContext}
import org.goldenport.g3.messages.html.Css
import org.goldenport.sdoc.SDoc

/**
 * @since   Sep. 21, 2010
 * @version Oct. 24, 2010
 * @author  ASAMI, Tomoharu
 */
class Html(val content: Node,
           val title: SDoc = "",
           val css: Css = null, // XXX
           val parameters: List[AnyRef] = Nil,
           val properties: Map[Symbol, Any] = Map.empty,
           val schema: Option[RecordSchema] = None) extends G3Message {
  def copy(title: SDoc = title, content: Node = content,
           parameters: List[AnyRef] = parameters,
           properties: Map[Symbol, Any] = properties,
           schema: Option[RecordSchema] = schema): Html = {
    new Html(content, title, css, parameters, properties, schema)
  }

  def +=(param: AnyRef) = {
    copy(parameters = parameters ::: List(param))
  }

  def ++=(params: List[AnyRef]) = {
    copy(parameters = parameters ::: params)
  }

  def put(key: Symbol, value: Any) = {
    copy(properties = properties + (key -> value))
  }

  def schema(schema: RecordSchema) = {
    copy(schema = Some(schema))
  }

  def write(response: HttpServletResponse, ctx: G3AgentContext) {
    response.setContentType("text/html;charset=utf-8")
    response.setStatus(HttpServletResponse.SC_OK)
    response.getWriter().println(toText(ctx))
  }

  def toText: String = {
    new PlainHtmlMaker().make
  }

  def toText(ctx: G3AgentContext): String = {
    ctx.masterHtml.inject(Html.this, ctx).to_text(ctx)
  }

  private def to_text(ctx: G3AgentContext): String = {
    new ContextHtmlMaker(ctx).make
  }

  def evalContent(ctx: G3AgentContext): Node = {
    new ContextHtmlMaker(ctx).evalContent
  }

  def inject(part: Html, ctx: G3AgentContext): Html = {
    val title = part.title
    val p = part.evalContent(ctx)
    if (p.label == "body") Html(title, p)
    else {
      val evaled = p.attribute("id") match {
        case Some(nodes) => substitute_id(evalContent(ctx), nodes.text, p)
        case None => substitute_id(evalContent(ctx), "article-content", p)
      }
      Html(title, evaled)
    }
  }

  private def is_same_id(lhs: Node, rhs: Node): Boolean = {
    val lid = lhs.attribute("id")
    val rid = rhs.attribute("id")
    if (!(lid.isDefined && rid.isDefined)) false
    else if (lid.get.text == rid.get.text) true
    else false
  }

  private def is_same_id(lhs: Node, id: String): Boolean = {
    val lid = lhs.attribute("id")
    if (!(lid.isDefined)) false
    else if (lid.get.text == id) true
    else false
  }

  private def substitute_id(master: Node, id: String, part: Node): Node = {
    if (is_same_id(master, id)) part
    else master match {
      case e: Elem => new Elem(
        e.prefix, e.label, e.attributes, e.scope,
        e.child.map(substitute_id(_, id, part)):_*)
      case t: Text => Text(t.text)
      case a: Atom[_] => a
    }
  }

  override def toString = {
    "HTML:" + makeShortString(new PlainHtmlMaker().makeBody, 80)
  }

  abstract class HtmlMaker {
    def make: String

    def transform(transformer: PartialFunction[Node, Node]): Html = {
      copy(title, transform_node(content, transformer))
    }

    private def transform_node(
      node: Node,
      transformer: PartialFunction[Node, Node]
    ): Node ={
      if (transformer.isDefinedAt(node)) transformer(node)
      else node match {
        case e: Elem => {
          new Elem(
            e.prefix, e.label, e.attributes, e.scope,
            e.child.map(transform_node(_, transformer)):_*)
        }
        case t: Text => t
        case a: Atom[_] => a
      }
    }

    private def make_body0 = {
      val e = eval_node(content)
      if (e.label == "body") e
      else <body>{e}</body>
    }

    protected def make_Css: Node

    def makeBody = {
//println("makeBody in = " + content)
      val e = eval_node(content)
//println("makeBody out = " + e)
      if (e.label == "body") e
      else <body>{e}</body>
      /*
       eval_node(content) match {
       case e @ Elem(_, "body", attr, _, _) => e
       case e => <body>{e}</body>
       }
       */
    }

    def evalContent = {
      eval_node(content)
    }

    private def eval_node(node: Node): Node = {
      //    println("eval_node: " + node + "," + node.getClass)
      val r = node match {
        case e: Elem if e.label == "g.list" => make_g3_list(e)
        case e: Elem if e.label == "g.detail" => make_g3_detail(e)
        case e: Elem if e.label == "g.input" => make_g3_input(e)
        case e: Elem if e.label == "g.confirm" => make_g3_confirm(e)
        case e: Elem if e.label == "g.string" => make_g3_string(e)
        case e: Elem if e.label == "g.login" => make_g3_login(e)
        case e: Elem if e.label == "g.button" => make_g3_button(e)
        case e: Elem => new Elem(
          e.prefix, e.label, eval_attrs(e.attributes), e.scope,
          e.child.map(eval_node):_*)
        case t: Text => t
        case a: Atom[_] => a
      }
      //    println("eval_node out: " + r)
      r
    }

    private def eval_attrs(attrs: MetaData): MetaData = {
      def eval_value(attr: Attribute): Option[Seq[Node]] = {
        Some(Text(eval_link(attr.value.text)))
      }

      attrs match {
        case Null => Null
        case attr: UnprefixedAttribute if attr.key == "g.href" => {
          new UnprefixedAttribute("href", eval_value(attr), eval_attrs(attr.next))
        }
        case attr: Attribute => attr.copy(eval_attrs(attr.next))
      }
    }

    private def eval_link(link: String) = {
      if (link == "/") base_Uri
      else if (link.startsWith("/")) base_Uri + link.substring(1)
      else link
    }

    protected def base_Uri: String

    private def findRecords: Option[RecordSet] = {
      parameters.find(_.isInstanceOf[RecordSet]).asInstanceOf[Option[RecordSet]]
    }

    private def findRecord: Option[Record] = {
      parameters.find(_.isInstanceOf[Record]).asInstanceOf[Option[Record]] orElse
      (parameters.find(_.isInstanceOf[RecordSet]) map {
        case rs: RecordSet => rs.head
      })
    }

    private def findString: Option[String] = {
      parameters.find(_.isInstanceOf[String]).asInstanceOf[Option[String]]
    }

    private def make_g3_list(elem: Elem) = {
      <table>
      <thead>
      {make_list_column_names}
      </thead>
      <tbody>
      {make_list_fields}
      </tbody>
      </table>
    }

    private def make_list_column_names = {
      if (!schema.isEmpty) make_list_column_names_schema
      else make_list_column_names_record
    }

    private def make_list_column_names_schema = {
      <tr>{
        for (f <- schema.get.fields) yield {
          val name = f.name.name
          <th>{name}</th>
        }      
      }</tr>
    }

    private def make_list_column_names_record = {
      <tr>{
        findRecords match {
          case Some(rs) if !rs.isEmpty => {
            for (f <- rs.head.fields) yield {
              val name = f._1.name
              <th>{name}</th>
            }
          }
          case _ => <th></th>
        }
      }</tr>
    }

    private def make_list_fields = {
      findRecords match {
        case Some(rs) => make_list_fields_records(rs)
        case None => Text("***Unkonwn***")
      }
    }

    private def make_list_fields_records(records: RecordSet) = {
      if (!schema.isEmpty) make_list_fields_records_schema(records)
      else make_list_fields_records_record(records)
    }

    private def make_list_fields_records_schema(records: RecordSet) = {
      //for (r <- records) {
      //  println("make_list_fields: " + r)
      //}
      for (r <- records) yield {
        <tr>{
          for (f <- schema.get.fields) yield {
            <td>{make_data(f, r)}</td>
          }
        }</tr>
      }
    }

    private def make_list_fields_records_record(records: RecordSet) = {
      //for (r <- records) {
      //  println("make_list_fields: " + r)
      //}
      for (r <- records) yield {
        <tr>{
          for ((_, v) <- r.fields) yield {
            <td>{make_value(v)}</td>
          }
        }</tr>
      }
    }

    private def make_g3_detail(elem: Elem) = {
      findRecord match {
        case Some(r) => make_g3_detail_record(elem, r)
        case None => Text("***Unkonwn***")
      }
    }

    private def make_g3_detail_record(elem: Elem, record: Record) = {
      //    println("make_g3_detail_record:" + record)
      def make_detail_fields = {
        if (schema != null) make_detail_fields_schema
        else make_detail_fields_without_schema
      }

      def make_detail_fields_schema = {
        for (f <- schema.get.fields) yield {
          val name = f.name.name
          val value = make_data(f, record)
          <tr>
          <td>{name}</td>
          <td>{value}</td>
          </tr>
        }
      }

      def make_detail_fields_without_schema = {
        for ((k, v) <- record.fields) yield {
          val name = k.name
          val value = v
          <tr>
          <td>{name}</td>
          <td>{value}</td>
          </tr>
        }
      }

      <table>
      <thead>
      <tr>
      <th>Item</th>
      <th>Value</th>
      </tr>
      </thead>
      <tbody>
      {make_detail_fields}
      </tbody>
      </table>
    }

    private def make_g3_input(elem: Elem) = {
      def make_input_fields = {
        for (f <- schema.get.fields) yield {
          val name = f.name.name
          <tr>
          <td>{name}</td>
          <td><input type="text" name={name}/></td>
          </tr>
        }
      }

      val action = elem.attribute("action") match {
        case Some(v) => eval_link(v.text)
        case None => no_action_uri
      }
      val submitvalue = elem.attribute("label") match {
        case Some(v) => v.text
        case None => "Submit"
      }
      <form method="post" action={action}>
      <table>
      <tbody>
      {make_input_fields}
      </tbody>
      </table>
      <input type="submit" value={submitvalue}/>
      </form>
    }

    private def no_action_uri = base_Uri + "_/no_action"

    private def make_g3_confirm(elem: Elem) = {
      findRecord match {
        case Some(r) => make_g3_confirm_record(elem, r)
        case None => Text("***Unkonwn***")
      }
    }

    private def make_g3_confirm_record(elem: Elem, record: Record) = {
      def make_confirm_fields = {
        for (f <- schema.get.fields) yield {
          val name = f.name.name
          val value = make_data(f, record)
          <tr>
          <td>{name}</td>
          <td>{value}</td>
          <input type="hidden" name={name} value={value}/>
          </tr>
        }
      }

      val action = elem.attribute("action") match {
        case Some(v) => eval_link(v.text)
        case None => no_action_uri
      }
      val submitvalue = elem.attribute("label") match {
        case Some(v) => v.text
        case None => "Submit"
      }
      <form method="post" action={action}>
      <table>
      <tbody>
      {make_confirm_fields}
      </tbody>
      </table>
      <input type="submit" value={submitvalue}/>
      </form>
    }

    private def make_g3_string(elem: Elem) = {
      findString match {
        case Some(s) => Text(s)
        case None => Text("***Unkonwn***")
      }
    }

    private def make_g3_login(elem: Elem) = {
      val action = "login"
      val titletext = "Login"
      val submitvalue = "Login"
      val usernametext = "User"
      val passwordtext = "Password"
      val (username: String, message) = findRecord match {
        case Some(r) => (r.getOrElse('username, ""),
                         r.getOrElse('message, ""))
        case None => ("", "")
      }

<body>
<header>
<div id="header">
<div id="header-content">
<h1 class="login">{titletext}</h1>
</div>
</div>
</header>
<div class="login">
<div>{message}</div>
<form method="post" action={action}>
<table class="login">
<tbody>
<tr>
  <th>{usernametext}</th>
  <td><input type="text" name="username" value={username} /></td>
</tr>
<tr>
  <th>{passwordtext}</th>
  <td><input type="password" name="password" /></td>
</tr>
</tbody>
</table>
<div class="login-buttons">
<input class="login" type="submit" value={submitvalue}/>
</div>
</form>
</div>
</body>
    }

    private def make_data(field: RecordField, record: Record): String = {
      record.get(field.name) match {
        case Some(v) => make_value(field, v)
        case None => "-"
      }
    }

    private def make_value(field: RecordField, value: AnyRef): String = {
      field.datatype match {
        /* XXX
         case dt: XDecimal => value.toString
         case dt: XInteger =>
         case dt: XNonPositiveInteger =>
         case dt: XNegativeInteger =>
         case dt: XNonNegativeInteger =>
         case dt: XPositiveInteger =>
         case dt: XLong =>
         case dt: XInt =>
         case dt: XShort =>
         case dt: XByte =>
         case dt: XUnsignedLong =>
         case dt: XUnsignedInt =>
         case dt: XUnsignedShort =>
         case dt: XUnsignedByte =>
         case dt: XFloat =>
         case dt: XDouble =>
         case dt: XDateTime =>
         case dt: XTime =>
         case dt: XDate =>
         case dt: XDecimal =>
         */
        case _ => value.toString
      }
    }

    private def make_g3_button(elem: Elem) = {
      val action = elem.attribute("action") match {
        case Some(v) => eval_link(v.text)
        case None => no_action_uri
      }
      val submitvalue = elem.attribute("label") match {
        case Some(v) => v.text
        case None => "Submit"
      }

<form method="post" action={action}><input type="submit" value={submitvalue}/></form>
    }

    private def make_value(value: AnyRef): String = {
      value.toString
    }
  }

  class PlainHtmlMaker extends HtmlMaker {
    def make() = {
      val xml = <html><head><title>{title}</title>{make_Css}</head>{makeBody}</html>
      xml.toString
      error("XXX")
    }

    protected def make_Css = {
      <link rel="stylesheet" type="text/css" href="_/css/style.css"/>
    }

    protected def base_Uri: String = {
      "~/"
      error("XXX")
    }
  }

  class ContextHtmlMaker(val ctx: G3AgentContext) extends HtmlMaker {
    def make = {
      val xml = <html><head><title>{title}</title>{make_Css}</head>{makeBody}</html>
//    println("to_text out:" + xml.toString)
      xml.toString
    }

    protected def make_Css = {
      <style type="text/css">{ctx.masterCss}</style>
    }

    protected def base_Uri: String = {
      val x = ctx.httpBaseUri
      println("Html: httpBaseUri = " + x)
      x
    }
  }
}

object Html {
  def apply(title: SDoc, body: Node): Html = {
    new Html(body, title = title)
  }

  def apply(title: SDoc, body: String): Html = {
    new Html(Text(body), title = title)
  }
  /*
   def withRecord(title: String, body: String, record: Record): Html = {
   new Html(title, List(Text(body)), record = record)
   }

def withRecordSet(title: String, body: String, records: RecordSet): Html = {
new Html(title, List(Text(body)), records = records)
}

def withSchema(title: String, body: String, schema: RecordSchema): Html = {
new Html(title, List(Text(body)), schema = schema)
}
*/
}
